﻿//
// JobManagerControl.cs: Manages jobs activated by worker threads.
//
// Copyright 2010 Michael Goethe
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using S9PalCommon;

namespace S9Pal
{
    /// <summary>
    /// Manages jobs activated by worker threads.
    /// </summary>
    public partial class JobManagerControl : UserControl
    {
        #region Fields

        private const int c_FinishedJobRemoveDelay = 60000;

        private delegate void RemoveJobDelegate(JobViewerControl job);

        private List<JobViewerControl> jobs = new List<JobViewerControl>();
        private int currentDisplayedJob = -1;

        bool isPauseResumeHovering = false;
        bool isStopHovering = false;
        bool isPreviousHovering = false;
        bool isNextHovering = false;

        #endregion

        #region Constructors

        public JobManagerControl()
        {
            InitializeComponent();

            this.ControlAdded += new ControlEventHandler(JobManagerControl_ControlAdded);
            this.ControlRemoved += new ControlEventHandler(JobManagerControl_ControlRemoved);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Job currently being displayed on this control.
        /// </summary>
        public int CurrentDisplayedJob
        {
            get
            {
                return currentDisplayedJob;
            }
            private set
            {
                currentDisplayedJob = value;                  
            }
        }

        /// <summary>
        /// Number of jobs added to this control.
        /// </summary>
        public int JobCount
        {
            get
            {
                return jobs.Count;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Add a job to the JobManagerControl.
        /// </summary>
        /// <param name="job">Job to add.</param>
        public void AddJob(JobViewerControl job)
        {
            this.Controls.Add(job);
        }

        /// <summary>
        /// Display the job at the specified index.
        /// </summary>
        /// <param name="index">Index of the job to display.</param>
        public void DisplayJob(int index)
        {
            if (JobCount > 0)
            {
                if (index >= 0 && index < jobs.Count)
                {
                    // Hide previously displayed job and remove its event handler.
                    if (CurrentDisplayedJob >= 0)
                    {
                        jobs[CurrentDisplayedJob].Visible = false;
                        jobs[CurrentDisplayedJob].Worker.StatusChanged -= new EventHandler(Worker_StatusChanged);
                    }

                    // Update the currently displayed job with the index passed in.
                    CurrentDisplayedJob = index;

                    // Show the new job and add an event handler.
                    jobs[CurrentDisplayedJob].Visible = true;
                    jobs[CurrentDisplayedJob].Worker.StatusChanged += new EventHandler(Worker_StatusChanged);
                }
                else
                {
                    throw new ArgumentOutOfRangeException();
                }

                UpdateJobIndexLabel();
                UpdatePauseResumePictureBox();
            }
        }

        /// <summary>
        /// Remove a job from the JobManagerControl.
        /// </summary>
        /// <param name="job"></param>
        public void RemoveJob(JobViewerControl job)
        {
            this.Controls.Remove(job);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Get image from resources directory by file name.
        /// </summary>
        /// <param name="fileName">File name of the image (only the name, not a path).</param>
        /// <returns>Image at the specified file name.</returns>
        private Image GetImageFromResources(String fileName)
        {
            return new Bitmap(System.Reflection.Assembly.GetEntryAssembly().GetManifestResourceStream("S9Pal.Resources." + fileName));
        }

        /// <summary>
        /// Gets the JobViewerControl associated to a given worker.
        /// </summary>
        /// <param name="worker">Worker to get the job of.</param>
        /// <returns>JobViewerControl associated to worker; null if not found.</returns>
        private JobViewerControl GetJobByWorker(Worker worker)
        {
            for (int i = 0; i < jobs.Count; i++)
            {
                if (jobs[i].Worker == worker)
                {
                    return jobs[i];
                }
            }

            return null;
        }

        /// <summary>
        /// Update the job index label based upon the currently displayed job.
        /// </summary>
        private void UpdateJobIndexLabel()
        {
            if (JobCount > 0)
                jobsLabel.Text = "Job " + (CurrentDisplayedJob + 1) + " of " + JobCount;
            else
                jobsLabel.Text = "No Active Jobs";
        }

        /// <summary>
        /// Update the pause/resume graphic depending on the hover state and worker status.
        /// </summary>
        private void UpdatePauseResumePictureBox()
        {
            if (CurrentDisplayedJob >= 0)
            {
                if (isPauseResumeHovering)
                {
                    // Mouse is hovering.
                    if (jobs[CurrentDisplayedJob].Worker.Status == WorkerStatus.Paused)
                        pauseResumePictureBox.BackgroundImage = GetImageFromResources("resumehover.png");
                    else
                        pauseResumePictureBox.BackgroundImage = GetImageFromResources("pausehover.png");
                }
                else
                {
                    // Mouse is not hovering.
                    if (jobs[CurrentDisplayedJob].Worker.Status == WorkerStatus.Paused)
                        pauseResumePictureBox.BackgroundImage = GetImageFromResources("resume.png");
                    else
                        pauseResumePictureBox.BackgroundImage = GetImageFromResources("pause.png");
                }
            }
        }

        #endregion

        #region Events

        private void JobManagerControl_ControlAdded(object sender, ControlEventArgs e)
        {
            // If the control is a job viewer, add it to the jobs List and display the newly added job.
            if (e.Control is JobViewerControl)
            {
                e.Control.Location = new Point(20, 15);
                jobs.Add((JobViewerControl)e.Control);
                DisplayJob(JobCount - 1);
                UpdateJobIndexLabel();
            }
        }

        private void JobManagerControl_ControlRemoved(object sender, ControlEventArgs e)
        {
            // If the control is a job viewer, remove it from the jobs List.
            if (e.Control is JobViewerControl)
            {
                jobs.Remove((JobViewerControl)e.Control);

                // Update CurrentDisplayJob in case it's out of range from the removal.
                if (JobCount == 0)
                    CurrentDisplayedJob = -1;
                else if (CurrentDisplayedJob > JobCount - 1)
                    CurrentDisplayedJob = JobCount - 1;

                DisplayJob(CurrentDisplayedJob);
                UpdateJobIndexLabel();
            }
        }

        private void nextPictureBox_Click(object sender, EventArgs e)
        {
            // Get the next index and make sure it's in range. If it's out of range, then wrap it back to zero.
            int nextIndex = CurrentDisplayedJob + 1;
            if (nextIndex >= JobCount)
                nextIndex = 0;

            // Display the job at the new index.
            DisplayJob(nextIndex);
        }

        private void nextPictureBox_MouseLeave(object sender, EventArgs e)
        {
            // Update the image to the non-hovering icon.
            nextPictureBox.BackgroundImage = GetImageFromResources("arrowright.png");
            isNextHovering = false;
        }

        private void nextPictureBox_MouseMove(object sender, MouseEventArgs e)
        {
            // Update the image to the hovering icon.
            if (!isNextHovering)
            {
                nextPictureBox.BackgroundImage = GetImageFromResources("arrowrighthover.png");
                isNextHovering = true;
            }
        }

        private void pauseResumePictureBox_Click(object sender, EventArgs e)
        {
            // First make sure there's a job to pause/resume.
            if (CurrentDisplayedJob >= 0)
            {
                // Get the worker tied to the job.
                Worker worker = jobs[CurrentDisplayedJob].Worker;

                // Give the command to either pause or resume, depending upon the current state.
                if (worker.Status == WorkerStatus.Active)
                    worker.Pause();
                else if (worker.Status == WorkerStatus.Paused)
                    worker.Start();
            }
        }

        private void pauseResumePictureBox_MouseLeave(object sender, EventArgs e)
        {
            // Update the picture box (non-hover).
            isPauseResumeHovering = false;
            UpdatePauseResumePictureBox();
        }

        private void pauseResumePictureBox_MouseMove(object sender, MouseEventArgs e)
        {
            // Update the picture box (hover).
            if (!isPauseResumeHovering)
            {
                isPauseResumeHovering = true;
                UpdatePauseResumePictureBox();
            }
        }

        private void previousPictureBox_Click(object sender, EventArgs e)
        {
            // Get the previous index and make sure it's in range. If it's out of range, then wrap it to the last job.
            int previousIndex = CurrentDisplayedJob - 1;
            if (previousIndex < 0)
                previousIndex = JobCount - 1;

            // Display the job at the new index.
            DisplayJob(previousIndex);
        }

        private void previousPictureBox_MouseLeave(object sender, EventArgs e)
        {
            // Update the image to the non-hovering icon.
            previousPictureBox.BackgroundImage = GetImageFromResources("arrowleft.png");
            isPreviousHovering = false;
        }

        private void previousPictureBox_MouseMove(object sender, MouseEventArgs e)
        {
            // Update the image to the hovering icon.
            if (!isPreviousHovering)
            {
                previousPictureBox.BackgroundImage = GetImageFromResources("arrowlefthover.png");
                isPreviousHovering = true;
            }
        }

        private void stopPictureBox_Click(object sender, EventArgs e)
        {
            // First make sure there's a job to stop.
            if (CurrentDisplayedJob >= 0)
            {
                // Get the worker tied to the job.
                Worker worker = jobs[CurrentDisplayedJob].Worker;

                // Issue the stop command.
                if (worker.Status == WorkerStatus.Active)
                    worker.Stop();
            }
        }

        private void stopPictureBox_MouseLeave(object sender, EventArgs e)
        {
            // Update the image to the non-hovering icon.
            stopPictureBox.BackgroundImage = GetImageFromResources("smallx.png");
            isStopHovering = false;
        }

        private void stopPictureBox_MouseMove(object sender, MouseEventArgs e)
        {
            // Update the image to the hovering icon.
            if (!isStopHovering)
            {
                stopPictureBox.BackgroundImage = GetImageFromResources("smallxhover.png");
                isStopHovering = true;
            }
        }

        private void Worker_StatusChanged(object sender, EventArgs e)
        {
            // Get the worker who fired the event.
            Worker worker = (Worker)sender;

            // Update the pause/resume icon to reflect any status changes.
            UpdatePauseResumePictureBox();

            if (worker.Status == WorkerStatus.Stopped)
            {
                // If the worker has been stopped, then remove the job.
                JobViewerControl job = GetJobByWorker(worker);
                BeginInvoke(new RemoveJobDelegate(RemoveJob), new object[1] { job });
            }
            else if (worker.Status == WorkerStatus.Finished)
            {
                // If the worker has finished, then wait some period of time before removing the job.
                JobViewerControl job = GetJobByWorker(worker);
                System.Threading.Thread.Sleep(c_FinishedJobRemoveDelay);
                BeginInvoke(new RemoveJobDelegate(RemoveJob), new object[1] { job });
            }
        }

        #endregion
    }
}
